package com.xcity.game.shop.mystery;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xcity.db.entity.Player;
import com.xcity.game.activity.ActivityService;
import com.xcity.game.activity.sevendays.SevenDay;
import com.xcity.game.activity2.ActivityIds;
import com.xcity.game.common.Cause;
import com.xcity.game.common.MessageConst;
import com.xcity.game.common.PlayerPool;
import com.xcity.game.event.GameEvents;
import com.xcity.game.item.ItemTemplate;
import com.xcity.game.shop.mystery.template.MysteryShopItemTemplate;
import com.xcity.game.shop.mystery.template.MysteryShopRefreshCostTemplate;
import com.xcity.game.shop.mystery.template.MysteryShopRefreshRuleTemplate;
import com.xcity.game.shop.mystery.template.RefreshRule;
import com.xcity.pomelo.PomeloRoute;
import com.xcity.pomelo.push.msg.ActivityStatePush;
import com.xcity.pomelo.push.msg.BatchItemsPush;
import com.xcity.pomelo.request.MysteryShopBuyRequest;
import com.xcity.pomelo.request.MysteryShopItemListRequest;
import com.xcity.pomelo.request.MysteryShopRefreshRequest;
import com.xcity.pomelo.response.HintResponse;
import com.xcity.pomelo.response.MysteryShopItemListResponse;
import com.xcity.pomelo.response.Response;
import com.xcity.util.RandomUtils;

import naga.x.App;
import naga.x.common.Updatable;
import naga.x.event.Event;
import naga.x.event.EventListener;
import naga.x.game.template.TemplateService;
import naga.x.game.time.DayListener;
import naga.x.game.time.TimeUpdater;
import naga.x.net.Session;
import naga.x.net.annotation.OPHandler;
import naga.x.net.impl.pomelo.annotation.PomeloOP;
import naga.x.service.ServiceException;
import naga.x.util.CommonUtils;
import naga.x.util.TimeUtils;

/**
 * 神秘商店
 * @author yang.li
 *
 */
@OPHandler
public class MysteryShopService extends ActivityService implements EventListener, DayListener, Updatable{
	
	private List<List<MysteryShopItemTemplate>> templates; // 按组归纳
	private Map<String, MysteryShopItemTemplate> templatesMap; // key: id-count
	private List<RefreshRule> refreshRules;
	private int refreshIndex;
	private List<MysteryShopRefreshCostTemplate> refreshCosts;
	@Deprecated
	private boolean opened;
	
	public static final int ITEM_NUM = 6; // 商店物品数
	public static final int NO_DISCOUNT = 100;
	private static final Random RND = new Random();
	
	private static final int GAP = 60000;
	long lastTime = 0L;
	
	protected static final Logger LOG = LoggerFactory.getLogger(MysteryShopService.class);
	
	protected Map<Long, Player> openedPlayers = new ConcurrentHashMap<>();
	
	class OpenShopTask implements Runnable {
		private RefreshRule rule;
		OpenShopTask(RefreshRule rule) {
			this.rule = rule;
		}
		@Override
		public void run() {
			MysteryShopService.this.opened = true;
			LOG.info("[MYSTERYSHOP OPENED]");
			long delay = rule.getEndTime() - TimeUpdater.getInstance().now();
			App.getApp().getSchedule().schedule(new CloseShopTask(), delay, TimeUnit.MILLISECONDS);
			notifyAllPlayers();
			LOG.info("[MYSTERYSHOP SCHEDULE CLOSE]TIME[{}]", TimeUtils.format(rule.getEndTime()));
		}
	}
	
	class CloseShopTask implements Runnable {
		@Override
		public void run() {
			MysteryShopService.this.opened = false;
			notifyAllPlayers();
			LOG.info("[MYSTERYSHOP CLOSED]");
			if (++refreshIndex >= refreshRules.size()) {
				// WAIT FOR TOMORROW
				LOG.info("[MYSTERYSHOP SCHEDULE OPEN]WAIT FOR TOMORROW");
				return;
			}
			while (true) {
				RefreshRule next = refreshRules.get(refreshIndex);
				long t = next.trigger();
				if (t != 0) {
					long delay = t - TimeUpdater.getInstance().now();
					App.getApp().getSchedule().schedule(new OpenShopTask(next), delay, TimeUnit.MILLISECONDS);
					LOG.info("[MYSTERYSHOP SCHEDULE OPEN]TIME[{}]", TimeUtils.format(t));
					break;
				}
				if (++refreshIndex >= refreshRules.size()) {
					LOG.info("[MYSTERYSHOP SCHEDULE OPEN]FAILED");
					break;
				}
			}
		}
	}
	
	@Override
	public void startup() throws ServiceException {
		List<MysteryShopItemTemplate> list = App.getApp().getServiceManager().get(TemplateService.class).getTemplatesList(MysteryShopItemTemplate.class);
		Map<Integer, List<MysteryShopItemTemplate>> group2templates = new LinkedHashMap<Integer, List<MysteryShopItemTemplate>>();
		templatesMap = new HashMap<String, MysteryShopItemTemplate>();
		for (int i = 0, size = list.size(); i < size; i++) {
			MysteryShopItemTemplate temp = list.get(i);
			List<MysteryShopItemTemplate> l = group2templates.get(temp.getGroup());
			if (l == null) {
				l = new ArrayList<MysteryShopItemTemplate>();
				group2templates.put(temp.getGroup(), l);
			}
			l.add(temp);
			templatesMap.put(temp.getId() + "-" + temp.getCount(), temp);
		}
		templates = new ArrayList<List<MysteryShopItemTemplate>>(group2templates.values());
		refreshCosts = App.getApp().getServiceManager().get(TemplateService.class).getTemplatesList(MysteryShopRefreshCostTemplate.class);
		initRefreshRules();
		App.getApp().getEventManager().register(this);
//		TimeUpdater.getInstance().addDayListener(this);
		App.getApp().getUpdater().addAsync(this);
	}
	
	private void initRefreshRules() {
		List<MysteryShopRefreshRuleTemplate> refs = App.getApp().getServiceManager().get(TemplateService.class).getTemplatesList(MysteryShopRefreshRuleTemplate.class);
		int size = refs.size();
		refreshRules = new ArrayList<RefreshRule>(size);
		for (int i = 0; i < size; i++) {
			MysteryShopRefreshRuleTemplate ref = refs.get(i);
			RefreshRule rule = new RefreshRule(ref);
			refreshRules.add(rule);
		}
		dayChanged();
	}

	@Override
	public void shutdown() throws ServiceException {
	}

	@Override
	public String getId() {
		return getClass().getName();
	}
	
	@Override
	public int[] getEventTypes() {
		return new int[] { /*GameEvents.EVENT_PLAYER_LOGIN,*/
				GameEvents.EVENT_PLAYER_LOGOUT,
				GameEvents.EVENT_SWEEP_DONE,
				GameEvents.EVENT_PLAYER_LOADED};
	}

	@Override
	public void handleEvent(Event event) {
		Player player;
		switch (event.getType()) {
		case GameEvents.EVENT_PLAYER_LOADED:
			player = event.getParameter();
			int leftTime = checkShop(player);
			if(leftTime > 0){
//						MysteryShopStatePush push = MysteryShopStatePush.OPEN;
//						push.setLeftSecond(leftTime);
//						player.send(push);
				if(!openedPlayers.containsKey(player.getId())){
					openedPlayers.put(player.getId(), player);
					player.send(new ActivityStatePush(ActivityIds.ACTIVITY_BLACKSHOP, STATE_OPENED));
				}
//					}else{
//						player.send(MysteryShopStatePush.CLOSE);
			}
			break;
		case GameEvents.EVENT_SWEEP_DONE:
			player = event.getParameter(0);
			int rate = event.getParameter(1);
			handleTriggerShop(player, rate);
			break;
		case GameEvents.EVENT_PLAYER_LOGOUT:
			player = event.getParameter();
			if(openedPlayers.containsKey(player.getId())){
				openedPlayers.remove(player.getId());
			}
			break;
		}
//		if (opened) {
//		}
	}
	
	@PomeloOP(PomeloRoute.MYSTERY_SHOP_ITEMS)
	protected void requestShopItems(Session session, MysteryShopItemListRequest req) {
//		if (!opened) {
//			session.send(new HintResponse(req.getSerial(), MessageConst.MYSTERY_SHOP_NOT_OPEN));
//			return;
//		}
		Player player = session.getClient();
		int leftTime = checkShop(player);
		if(leftTime <= 0){
			session.send(new HintResponse(req.getSerial(), MessageConst.MYSTERY_SHOP_CLOSED));
			return;
		}
		List<PlayerMysteryShopItem> list = getShopItems(player);
		int refreshCost = getRefreshCost(player);
		int cd = getRefreshCD();
		session.send(new MysteryShopItemListResponse(req.getSerial(), refreshCost, cd, list, getDiscount(player)));
	}
	
	@PomeloOP(PomeloRoute.MYSTERY_SHOP_BUY)
	protected void requestBuy(Session session, MysteryShopBuyRequest req) {
//		if (!opened) {
//			session.send(new HintResponse(req.getSerial(), MessageConst.MYSTERY_SHOP_NOT_OPEN));
//			return;
//		}
		Player player = session.getClient();
		int leftTime = checkShop(player);
		if(leftTime <= 0){
			session.send(new HintResponse(req.getSerial(), MessageConst.MYSTERY_SHOP_CLOSED));
			return;
		}
		List<PlayerMysteryShopItem> list = getShopItems(player);
		int index = req.getId();
		if (index < 0 || index >= list.size()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.SHOP_ITEM_NOT_EXIST));
			return;
		}
		PlayerMysteryShopItem item = list.get(index);
		if (item.state == PlayerMysteryShopItem.STATE_SELL_OUT) {
			session.send(new HintResponse(req.getSerial(), MessageConst.SHOP_ITEM_SOLD_OUT));
			return;
		}
//		new MysteryShopBuyOperation(player, item).send();
		ItemTemplate template = item.getTemplate().getRef();
		int needBagGridSize = template.isAutoUse() ? 0 : (item.getCount() + template.getStack() - 1) / template.getStack();
		if (player.getBag().getFreeSize() < needBagGridSize) {
			player.send(new HintResponse(req.getSerial(), MessageConst.BAG_FULL));
			return;
		}
		int cost = item.getTemplate().getPrice() * getDiscount(player) / 100;
		if (cost < 1) {
			cost = 1;
		}
		if (!player.cost(item.getTemplate().getPriceType(), cost, Cause.MYSTERY_SHOP_BUY)) {
			player.send(new HintResponse(req.getSerial(), MessageConst.STONE_NOT_ENOUTH));
			return;
		}
		item.setState(PlayerMysteryShopItem.STATE_SELL_OUT);
		player.getBag().addItemWithoutException(item.getId(), item.getCount(), Cause.MYSTERY_SHOP_BUY);
		session.send(new Response(req.getSerial()));
		session.send(new BatchItemsPush(item.getTemplate().getRef(), item.getCount()));
	}
	
	@PomeloOP(PomeloRoute.MYSTERY_SHOP_REFRESH)
	protected void requestRefresh(Session session, MysteryShopRefreshRequest req) {
		Player player = session.getClient();
		int cost = getRefreshCost(player);
		if (player.costStone(cost, Cause.MYSTERY_SHOP_REFRESH)) {
			List<PlayerMysteryShopItem> list = refresh(player);
			addManualRefreshTimes(player, 1);
			cost = getRefreshCost(player);
			int cd = getRefreshCD();
			session.send(new MysteryShopItemListResponse(req.getSerial(), cost, cd, list, getDiscount(player)));
			return;
		}
		session.send(new HintResponse(req.getSerial(), MessageConst.STONE_NOT_ENOUTH));
	}
	
	/**
	 * 是否需要刷新商店物品
	 * @param player
	 * @return
	 */
	private boolean needRefresh(Player player, List<PlayerMysteryShopItem> items) {
		if (items == null || items.isEmpty()) {
			return true;
		}
		int lastDay = player.getPool().getIntValue(PlayerPool.PROPERTY_MYSTERY_SHOP_LAST_REFRESH_DAY);
		if (lastDay != TimeUpdater.getInstance().today()) {
			return true;
		}
		int index = player.getPool().getIntValue(PlayerPool.PROPERTY_MYSTERY_SHOP_LAST_REFRESH_INDEX);
		if (index != refreshIndex) {
			return true;
		}
		for (int i = 0, size = items.size(); i < size; i++) {
			PlayerMysteryShopItem item = items.get(i);
			if (item.getTemplate() == null) { // 配置表删除了该模板
				return true;
			}
		}
		return false;
	}
	
	private List<PlayerMysteryShopItem> refresh(Player player) {
		int lastDay = player.getPool().getIntValue(PlayerPool.PROPERTY_MYSTERY_SHOP_LAST_REFRESH_DAY);
		int index = player.getPool().getIntValue(PlayerPool.PROPERTY_MYSTERY_SHOP_LAST_REFRESH_INDEX);
		if (lastDay != TimeUpdater.getInstance().today() || index != refreshIndex) {
			player.poolSet(PlayerPool.PROPERTY_MYSTERY_SHOP_LAST_REFRESH_DAY, TimeUpdater.getInstance().today());
			player.poolSet(PlayerPool.PROPERTY_MYSTERY_SHOP_LAST_REFRESH_INDEX, refreshIndex);
			player.poolRemove(PlayerPool.PROPERTY_MYSTERY_SHOP_MANUAL_REFRESH_TIMES); // 重置手动刷新次数
		}
		
		List<List<MysteryShopItemTemplate>> dup = new ArrayList<List<MysteryShopItemTemplate>>(templates);
		CommonUtils.shuffle(dup);
		int size = ITEM_NUM;
		List<PlayerMysteryShopItem> list = new ArrayList<PlayerMysteryShopItem>(size);
		for (int i = 0, j = dup.size(); i < size && i < j; i++) {
			List<MysteryShopItemTemplate> l = dup.get(i);
			list.add(new PlayerMysteryShopItem(l.get(RND.nextInt(l.size()))));
		}
		player.getPool().put(PlayerPool.PROPERTY_MYSTERY_SHOP_ITEMS, list);
		return list;
	}
	
	private List<PlayerMysteryShopItem> getShopItems(Player player) {
		List<PlayerMysteryShopItem> list = player.getPool().getList(PlayerPool.PROPERTY_MYSTERY_SHOP_ITEMS, PlayerMysteryShopItem.class);
		if (needRefresh(player, list)) {
			list = refresh(player);
		}
		return list;
	}

	@Override
	public boolean dayChanged() {
		refreshIndex = 0;
		boolean scheduled = false;
		for (int i = 0; i < refreshRules.size(); i++) {
			RefreshRule rule = refreshRules.get(i);
			rule.refresh();
			LOG.info("[MYSTERYSHOP TIME REFRESH]" + rule.toString());
			long t;
			if (!scheduled && (t = rule.trigger()) != 0) {
				scheduled = true;
				refreshIndex = i;
				App.getApp().getSchedule().schedule(new OpenShopTask(rule), t - TimeUpdater.getInstance().now(), TimeUnit.MILLISECONDS);
				LOG.info("[MYSTERYSHOP SCHEDULE OPEN]TIME[{}]", TimeUtils.format(t));
			}
		}
		return true;
	}
	
	private int getRefreshCD() {
		int index = refreshIndex + 1;
		long t = refreshRules.get(index < refreshRules.size() ? index : 0).getTime(0);
		long cd = t - TimeUpdater.getInstance().now();
		return (int) (cd >= 0 ? cd : cd + TimeUnit.DAYS.toMillis(1));
	}
	
	private int getRefreshCost(Player player) {
		int lastDay = player.getPool().getIntValue(PlayerPool.PROPERTY_MYSTERY_SHOP_LAST_REFRESH_DAY);
		if (lastDay == 0L || lastDay != TimeUpdater.getInstance().today()) {
			return refreshCosts.get(0).getCost();
		}
		int index = player.getPool().getIntValue(PlayerPool.PROPERTY_MYSTERY_SHOP_LAST_REFRESH_INDEX);
		if (index != refreshIndex) {
			return refreshCosts.get(0).getCost();
		}
		int n = player.getPool().getIntValue(PlayerPool.PROPERTY_MYSTERY_SHOP_MANUAL_REFRESH_TIMES);
		return refreshCosts.get(n < refreshCosts.size() ? n : refreshCosts.size() - 1).getCost();
	}
	
	public int getDiscount(Player player) {
		int lastDay = player.getPool().getIntValue(PlayerPool.PROPERTY_MYSTERY_SHOP_LAST_REFRESH_DAY);
		if (lastDay == 0L || lastDay != TimeUpdater.getInstance().today()) {
			return NO_DISCOUNT;
		}
		int index = player.getPool().getIntValue(PlayerPool.PROPERTY_MYSTERY_SHOP_LAST_REFRESH_INDEX);
		if (index != refreshIndex) {
			return NO_DISCOUNT;
		}
		int n = player.getPool().getIntValue(PlayerPool.PROPERTY_MYSTERY_SHOP_MANUAL_REFRESH_TIMES) - 1;
		if (n < 0) {
			return NO_DISCOUNT;
		}
		MysteryShopRefreshCostTemplate temp = refreshCosts.get(n < refreshCosts.size() ? n : refreshCosts.size() - 1);
		return temp.getDiscount();
	}
	
	private void addManualRefreshTimes(Player player, int n) {
		player.poolAdd(PlayerPool.PROPERTY_MYSTERY_SHOP_MANUAL_REFRESH_TIMES, n);
	}
	
	private void notifyAllPlayers() {
//		MysteryShopStatePush push = opened ? MysteryShopStatePush.OPEN : MysteryShopStatePush.CLOSE;
//		Server.broadcast(push);
	}
	
	public MysteryShopItemTemplate getTemplate(int id, int count) {
		return templatesMap.get(id + "-" + count);
	}
	
	protected void handleTriggerShop(Player player, int rate){
		int ran = RandomUtils.getRandomNum(1, 10000);
		if(ran < rate){
			long now = TimeUpdater.getInstance().now();
			long lastHideTime = player.getPool().getLong(PlayerPool.PROPERTY_BLACKSHOP_HIDE_TIME, 0);
			lastHideTime = now + 10 * 60 * 1000L;
			player.getPool().put(PlayerPool.PROPERTY_BLACKSHOP_HIDE_TIME, lastHideTime);
//			int leftSecond = (int)((lastHideTime - now) / 1000L);
//			MysteryShopStatePush push = MysteryShopStatePush.OPEN;
//			push.setLeftSecond(leftSecond);

			if(!openedPlayers.containsKey(player.getId())){
				openedPlayers.put(player.getId(), player);
				player.send(new ActivityStatePush(ActivityIds.ACTIVITY_BLACKSHOP, STATE_OPENED));
			}
		}
	}
	
	protected int checkShop(Player player){
		int leftTime = -1;
		long now = TimeUpdater.getInstance().now();
		long lastHideTime = player.getPool().getLong(PlayerPool.PROPERTY_BLACKSHOP_HIDE_TIME, 0);
		if(lastHideTime > now && lastHideTime != 0){
			int leftSecond = (int)((lastHideTime - now) / 1000L);
			leftTime = leftSecond;
		}
		return leftTime;
	}
	
	protected void checkPlayerShop(){
		Collection<Player> players = openedPlayers.values();//App.getApp().getServiceManager().get(PlayerService.class).getOnlinePlayers();
		for(Player player : players){
			if(openedPlayers.containsKey(player.getId())){
				int leftTime = checkShop(player);
				if(leftTime <= 0){
					player.send(new ActivityStatePush(ActivityIds.ACTIVITY_BLACKSHOP, STATE_CLOSED));
					openedPlayers.remove(player.getId());
				}
			}
		}
	}

	@Override
	public boolean update() {
		if(TimeUpdater.getInstance().now() - lastTime > GAP){
			lastTime = TimeUpdater.getInstance().now();
			checkPlayerShop();
		}
		return false;
	}
	
}
